﻿namespace MefContrib.Models.Provider.Tests
{
    using System;
    using NUnit.Framework;

    [TestFixture]
    public class ComposableMethodTests
    {
        [Test]
        public void DeclaringTypeShouldReturnCorrectClass()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnNoParameters")[0]);

            Assert.IsTrue(method.DeclaringType == typeof(FakeMethodPart));
        }

        [Test]
        public void ReturnTypeShouldReturnCorrectType()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnNoParameters")[0]);

            Assert.IsTrue(method.ReturnType == typeof(void));
        }

        [Test]
        public void MethodShouldNotBeWritable()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnNoParameters")[0]);

            Assert.IsFalse(method.IsWritable);
        }

        [Test]
        public void MethodShouldBeReadable()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnNoParameters")[0]);

            Assert.IsTrue(method.IsReadable);
        }

        [Test]
        public void StaticMethodShouldNotNeedInstance()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("StaticNoReturnNoParameters")[0]);

            Assert.IsFalse(method.IsInstanceNeeded);
        }

        [Test]
        public void NonStaticMethodShouldNeedInstance()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnNoParameters")[0]);

            Assert.IsTrue(method.IsInstanceNeeded);
        }

        [Test]
        public void MethodShouldNotBeNull()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnNoParameters")[0]);

            Assert.IsNotNull(method.Method);
        }
        
        [Test, ExpectedException(typeof(InvalidOperationException))]
	    public void SetValueShouldThrowException()
	    {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnNoParameters")[0]);

            FakeMethodPart part =
                new FakeMethodPart();

            method.SetValue(part, null);
        }
        
        [Test]
        public void GetValueShouldNotFailIfMethodHasNoReturnNoParameters()
	    {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnNoParameters")[0]);
            
            FakeMethodPart part =
                new FakeMethodPart();

            object returnValue =
                method.GetValue(part);

            Assert.IsTrue(returnValue.GetType() == typeof(Action));
        }

        [Test]
        public void GetValueShouldNotFailIfMethodHasNoReturnFourParameters()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnFourParameters")[0]);

            FakeMethodPart part =
                new FakeMethodPart();

            object returnValue =
                method.GetValue(part);

            Assert.IsTrue(returnValue.GetType().GetGenericTypeDefinition() == typeof(Action<,,,>));
        }

        [Test, ExpectedException(typeof(InvalidOperationException))]
        public void GetValueShouldFailIfMethodHasNoReturnAndMoreThanFourParameters()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("NoReturnFiveParameters")[0]);

            FakeMethodPart part =
                new FakeMethodPart();

            object returnValue =
                method.GetValue(part);
        }

        [Test]
        public void GetValueShouldNotFailIfMethodHasReturnNoParameters()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("IntReturnNoParameters")[0]);

            FakeMethodPart part =
                new FakeMethodPart();

            object returnValue =
                method.GetValue(part);

            Assert.IsTrue(returnValue.GetType().GetGenericTypeDefinition() == typeof(Func<>));
        }

        [Test]
        public void GetValueShouldNotFailIfMethodHasReturnFourParameters()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("IntReturnFourParameters")[0]);

            FakeMethodPart part =
                new FakeMethodPart();

            object returnValue =
                method.GetValue(part);

            Assert.IsTrue(returnValue.GetType().GetGenericTypeDefinition() == typeof(Func<,,,,>));
        }

        [Test, ExpectedException(typeof(InvalidOperationException))]
        public void GetValueShouldFailIfMethodHasReturnAndMoreThanFourParameters()
        {
            ComposableMethod method =
                new ComposableMethod(typeof(FakeMethodPart).GetMember("IntReturnFiveParameters")[0]);

            FakeMethodPart part =
                new FakeMethodPart();

            object returnValue =
                method.GetValue(part);
        }
    }

    public class FakeMethodPart
    {
        public void NoReturnNoParameters()
        {
        }

        public void NoReturnFourParameters(int a, string b, decimal c, bool d)
        {
        }

        public void NoReturnFiveParameters(int a, string b, decimal c, bool d, object e)
        {
        }

        public int IntReturnNoParameters()
        {
            return 15;
        }

        public int IntReturnFourParameters(int a, string b, decimal c, bool d)
        {
            return 1;
        }

        public int IntReturnFiveParameters(int a, string b, decimal c, bool d, object e)
        {
            return 1;
        }

        public static void StaticNoReturnNoParameters()
        {
        }
    }
}